package com.jf.basic.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.function.Function;

/**
 * 文件递归 拜访者 模式
 *
 * @author Junfeng
 */
@Slf4j
public class WalkFilesVisitor<R> extends SimpleFileVisitor<Path> {

    private long totalDirs;
    private long totalFiles;
    private long totalSizes;

    private long batchNum;
    private long currentSize;
    private Set<File> files;

    private long batchSize;
    private Function<Set<File>, R> func;


    public WalkFilesVisitor(int batchSize, Function<Set<File>, R> func) {
        this.files = new HashSet<>((int) (batchSize * 1.75));
        this.batchSize = batchSize;
        this.func = func;
    }

    /**
     * 访问目录中子文件前执行
     */
    @Override
    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
        currentSize++;
        totalDirs++;
        files.add(dir.toFile());
        invoke();
        return super.preVisitDirectory(dir, attrs);
    }

    /**
     * 访问文件时执行
     */
    @Override
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
        currentSize++;
        totalFiles++;
        File f = file.toFile();
        totalSizes = totalSizes + f.length();
        files.add(f);
        invoke();
        return super.visitFile(file, attrs);
    }

    /**
     * 文件访问失败时执行
     */
    @Override
    public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
        log.warn("WARN: " + file.toString() + " 访问失败");
        return super.visitFileFailed(file, exc);
    }

    /**
     * 文件夹访问完成后执行
     */
    @Override
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
        return super.postVisitDirectory(dir, exc);
    }

    private void invoke() {
        if (batchSize == currentSize && currentSize > 0) {
            batchNum++;
            currentSize = 0;
            flash();
        }
    }

    public void flash() {
        if (this.currentSize == 0) {
            log.info("walk files info: batch:{} \t dirs: {} \t files: {} \t sizes: {}", getBatchNum(), getTotalDirs(), getTotalFiles(), getTotalSizes());
        } else {
            // 如果时最后刷新的一次，则批次号加一
            log.info("walk files info: batch:{} \t dirs: {} \t files: {} \t sizes: {}", getBatchNum() + 1, getTotalDirs(), getTotalFiles(), getTotalSizes());
        }
        func.apply(Collections.unmodifiableSet(files));
        files.clear();
    }

    public long getBatchNum() {
        return batchNum;
    }

    public long getTotalDirs() {
        return totalDirs;
    }

    public long getTotalFiles() {
        return totalFiles;
    }

    public long getTotalSizes() {
        return totalSizes;
    }
}
